Type Members, Enumerations, Casting

When using inheritance to store derived instances in a generalized data structure, it is often necessary to re-transform the elements into their derived states in order to access more specific data members. Technically, you could achieve this using virtual functions, but that would force you to have generalized functions where they do not make sense.

If you know exactly what type an object really is, you can simply cast the pointer to the more specific type.

dog newDog;
animal* something = &newDog;

dog* someDog = (dog*)something;

“someDog” now points to “newDog” as a “dog” instead of just an “animal.”

However, you usually won’t know the original type of the object. There are two common ways to get around this: specific “type” members and the dynamic_cast operator. A “type” member is what it sounds like—the base class defines a data member describing the specific type of the object. Each of derived class (in its constructor) sets that member to denote the specific type.

Enumerations, or enums for short, are often used for this purpose. An enum is a set of constant, related values. For example, you could create an enum called “class_types,” where each member signifies a specific derived type.

enum animal_types 
{
	animal_generic,
	animal_dog,
	animal_cat,
	animal_bird
};

This example allows you to use “animal_generic,” “animal_dog,” “animal_cat,” and “animal_bird” as if they were constants of type “animal_types.” You can probably see why this is useful in creating “type” members.

class animal {
	animal();
	// stuff
	animal_types type;
};

animal::animal() {
	type = animal_generic;
}

class dog : public animal {
	dog();
	// stuff
};

dog::dog() {
	type = animal_dog;
}

Here, if we create an animal, the animal constructor will set the type to "animal_generic." However, if we were to create a dog, the animal constructor will still set the type to "animal_generic," but the dog constructor will then set it to "animal_dog."

Being a member of the base class, the type member is shared through all animals. Hence, if you have a pointer to a general animal, you can simply check what type it is. Once you have checked the type, you can cast the pointer to the derived type.

animal* something;	// This points to a cat, a dog, or a general animal

// Test for dog
if(something->type == animal_dog) {
	dog* someDog = (dog*)something;
}
// Test for cat
else if(something->type == animal_cat) {
	cat* someCat = (cat*)something;
}

Once cast, the full features of the derived class are available.

dynamic_cast

C++ provides a way to perform this type checking without a type member. This is with the dynamic_cast keyword. dynamic_cast takes two arguments: the type you want to cast to, and the pointer you want to cast. The type is, of course, a template parameter. For example, instead of the previous example (checking the type member before casting) you could do this...

dog* someDog = dynamic_cast<dog*>(something);

If you already know exactly what type you want to cast to, there’s not much point in using dynamic_cast. However—the useful thing about dynamic_cast is that if the cast fails, instead of giving you an invalid pointer, it returns a null pointer.

Hence, it is simple to test if a cast is successful. The previous example could be replaced with...

dog* someDog = dynamic_cast<dog*>(something);

// Test if dog cast succeeded
if(someDog)
{
	// ...
}
else // Not a dog
{
	cat* someCat = dynamic_cast<cat*>(something);
	// Test if cat cast succeeded
	if(someCat)
	{
		// ...
	}
}

Pure Virtual Functions & Abstract Classes

Often, it doesn’t make sense to instantiate an object of a base class type, or even for generalized logic to exist. You might even want only the bottom level of derived classes to exist. For example, a tree might start with a generic “entity,” which is a base for “player” and “monster,” which are in turn bases for “knight,” and “skeleton.” However, you may only want the final layer of entities (knights and skeletons) to actually be created.

You can restrict the creation of base class objects, but still provide shared functionality by the use of pure virtual functions. A class with one or more pure virtual functions is called an “abstract” class. An abstract class specifies the interface of derived classes, but cannot itself be created. However, pointers to abstract classes are completely valid if they point to a real object; a concrete derived type. This means that you can still store derived types under their base class—you just can't create objects of the base type.

The implementation of a pure virtual function is not defined, so there is no generalized logic—every derived class is forced to implement their own version or become abstract as well. The rules of virtual function calls still apply, except here, there is no generic option.

Defining a pure virtual function is extremely simple...

class animal {
public:			
	virtual void speak() = 0;	// '= 0' makes the function pure virtual
	// ...
};

Then, when creating your derived classes (say “dog”), you must redefine and implement the any inherited pure virtual functions, as again, there is no generic to fall back on.

class dog : public animal {
public:
	speak();
	// ...
};

dog::speak() {
	cout << “bark!” << endl;
}

Multiple Inheritance

Unlike many other object-oriented languages (such as Java), C++ supports multiple inheritance. This means a derived class can inherit from more than one base class. It works more or less how you’d expect: the derived class inherits the methods and data of both base classes, and can be polymorphically abstracted to either base class type.

Multiple inheritance is particularly useful in defining objects by building up different sets of functionality rather than extending the functionality of a more general class. For example, you could have a “car” class inherit from “controlledVehicle,” “usesFuel,” and “wheeledVehicle” base classes. Here, “car” must implement the interfaces from each of the component parts. In this paradigm, the components would likely be abstract classes that specific a set of functionality to be implemented in another class.

To specify multiple inheritance, simply add the more base classes delineated by commas:

class car : public wheeledVehicle, public controlledVehicle {
	// Implement abstract interfaces
	// ...
}

Virtual Inheritance

Although not immediately obvious, there is a big problem with multiple inheritance. This is called the “diamond” problem, because of what the problem inheritance tree looks like:

In this situation, class ‘D’ inherits from both ‘B’ and ‘C,’ which in turn both inherit from ‘A.’ So, if you were to create an object of type ‘D,’ where would the ‘A’ part come from? Under the hood, a derived class stores a pointer to an instance of its base class. Hence, because an object of type ‘D’ contains two objects of type ‘B’ and ‘C,’ both of those objects contain their own copy of ‘A.’ Therefore, if you were to call a virtual function from ‘A,’ which path would the call take? It's ambiguous.

These problems are solved by the use of virtual inheritance. In the example, if classes ‘B’ and ‘C’ virtually inherited from ‘A,’ an instance of ‘D’ will have only one ‘A’ part, hence any calls will be unambiguous. That's all there is to virtual inheritance.

You shouldn’t use virtual inheritance unless you have to, because it can cause significant performance overhead. Further, “diamond” inheritance is often a sign of bad design, as problems can still be created by ambiguous names and data access patterns. As with everything else, just make sure your design fits your problem.

To specify a class as virtually inherited, simply add the keyword “virtual”:

class A {
	void foo();
};

class B : public virtual A {
	// ...
}; 

class C : public virtual A {
	// ...
};

class D : public B, public C {
	// ...
}
 
int main() {
	D object;
	object.foo();
}

If ‘B’ and ‘C’ did not inherit virtually, the call to foo() would be ambiguous, as ‘object’ would contain two instances of ‘A.’

Programming Exercises

  1. Play around with the example! (Again)
  2. Test behavior in changing what is virtual (especially destructors), what inherits virtually, multiple inheritance, adding more classes, and generally complicating the inheritance tree. Then, test how casting behaves.

  3. Similarly to last lesson's second exercise, create your own inheritance tree, but this time using a component-based model with multiple and virtual inheritance. For example, entities in a game, or vehicles, or employees, or more animals.